package forum.server.controller;

import java.util.Vector;

import forum.server.domain.*;
import forum.server.persistence.*;
import forum.shared.Delimiters;
import forum.shared.exceptions.*;

import java.util.Arrays;
import java.util.Comparator;

/**
 * Facade DP.
 * Delegates messages to UserHandler & PostHandler classes.
 *
 * @author Mirsky
 *
 */
public class ForumFacade implements ForumFacadeInt {

	/** Object responsible for data persistence **/
	private DataManipulatorInt dm;

	/**
	 * Upon registration or login, the handlers should be created with
	 * their respective constructors with a logged-in-user as a parameter.
	 */
	private UserHandler uh;
	private PostHandler ph;
	
	/**
	 * References to logged user or NULL.
	 */
	private LoggedInUser loggedInUser;

	/**
	 * Search Engine interace.
	 */
	private SearchEngine se;

	/**
	 * Logger.
	 */
	private static MyLogger logger = MyLogger.getInstance();

	/**
	 * Constructor.
	 */
	public ForumFacade() throws DatabaseException {
		this.loggedInUser = null;
		this.dm = DataManipulatorProxy.getInstance();
		this.se = SearchAdapter.getInstance();
		this.uh = new UserHandler(dm, null);
		this.ph = new PostHandler(dm, se, loggedInUser);
		initSearchEngine(this.se);
	}

	/**
	 * Initiate SearchEngine - add posts stored in the DB to the engine.
	 * @param se The search engine
	 */
	public void initSearchEngine(SearchEngine se) throws DatabaseException {
		for (Post tThread : this.dm.getPosts()) {
			addPostToSE(se, tThread);
		}
	}

	/**
	 * Recursively add Post p and all it's replies to se.
	 * @param se The SearchEngine
	 * @param p the Post to add
	 */
	private void addPostToSE(SearchEngine se, Post p) throws DatabaseException {
		se.addData(p);
		for (Post tReply : this.dm.getReplies(p.getPostId())) {
			addPostToSE(se, tReply);
		}
	}

	/**
	 * Used to fine display the forum structure
	 * @param p - The post to display
	 * @param tab - number of tabs to use
	 * @return String containing the forum representation.
	 * @throws DatabaseException
	 */
	@SuppressWarnings("unused")
	private String threadAsTreeString(Post p, int tab) throws DatabaseException {
		String tabString = "";
		for (int i=0; i<tab; i++) {
			tabString+="\t";
		}
		String ans = p.printMe(tabString);
		Vector<Post> replies = this.dm.getReplies(p.getPostId());
		if (replies.size()>0) {
			for (Post po: replies) {
				ans+= "\n\n";
				ans+=tabString;
				ans+=threadAsTreeString(po,tab+1);
			}
		}
		return ans;
	}

	private String getRepliesView(Post p) throws DatabaseException{
		String ans = "";
		Integer id = p.getPostId();
		ans += Delimiters.POST_CH + id.toString() + Delimiters.DELIM + p.getAuthor() + Delimiters.DELIM + p.getHeader() + Delimiters.DELIM 
			+ p.getContent().getContent() + Delimiters.DELIM;
		
		
		Vector<Post> replies = dm.getReplies(p.getPostId());
		if (replies.size() != 0){
			ans += Delimiters.REPLIES_CH;
			for (Post reply : replies){
				ans += getRepliesView(reply);
			}
			ans += Delimiters.REPLIES_CH_END;
		}
		ans += Delimiters.POST_CH_PRE_END + id.toString() + Delimiters.POST_CH_END;
		return ans;	
	}

	public String viewForum() throws DatabaseException{
		Vector<Post> posts=null;
		posts = dm.getPosts();
        String ans = "";
		for (Post p : posts){
			ans += getRepliesView(p);
		}
		return ans;
	}
	
	/*
	 * (non-Javadoc)
	 * @see forum.server.business.ForumFacadeInt#register(java.lang.String, java.lang.String)
	 */
	public LoggedInUser register(String username, String pass)
	throws RegistrationException, DatabaseException {
		LoggedInUser tUser = this.uh.register(username, pass);
		this.ph = new PostHandler(this.dm, this.se, tUser);
		return tUser;
	}

	/*
	 * (non-Javadoc)
	 * @see forum.server.business.ForumFacadeInt#login(java.lang.String, java.lang.String)
	 */
	public LoggedInUser login(String username, String pass)
	throws IncorrectLoginException, NoSuchUserException, DatabaseException {
		this.loggedInUser = this.uh.login(username, pass);
		this.ph = new PostHandler(this.dm, this.se, this.loggedInUser);
		return this.loggedInUser;
	}

	/*
	 * (non-Javadoc)
	 * @see forum.server.business.ForumFacadeInt#logoff()
	 */
	public void logoff() {
		if (uh != null) {
			this.uh.logoff();
			this.ph = new PostHandler(this.dm, this.se, null);
			this.loggedInUser = null;
		}
	}

	/*
	 * (non-Javadoc)
	 * @see forum.server.business.ForumFacadeInt#addPost(java.lang.String, forum.server.domain.PostContent)
	 */
	public Post addPost(String header, PostContent pc)
	throws NotLoggedInException, DatabaseException {
		if (this.loggedInUser == null) {
			logger.logInfo("Add post: A guest user trying to add a post.");
			throw new NotLoggedInException("Only logged in users can add posts.");
		}
		return this.ph.addPost(header, pc);
	}

	/*
	 * (non-Javadoc)
	 * @see forum.server.business.ForumFacadeInt#modifyPost(int, forum.server.domain.PostContent)
	 */
	public Post modifyPost(int postId, PostContent pc)
	throws NotLoggedInException, UnauthorizedOpException,
	NoSuchPostException, DatabaseException {
		if (this.loggedInUser == null) {
			logger.logInfo("Modify post: A guest user trying to modify a post.");
            throw new NotLoggedInException("Only logged in users can modify posts.");
		}
		return this.ph.modifyPost(postId, pc);
	}

	/*
	 * (non-Javadoc)
	 * @see forum.server.business.ForumFacadeInt#replyToPost(int, java.lang.String, forum.server.domain.PostContent)
	 */
	public Post replyToPost(int parentId, String header, PostContent pc)
	throws NotLoggedInException, NoSuchPostException, DatabaseException {
		if (this.loggedInUser == null) {
			logger.logInfo("Add reply: A guest user trying to add a reply.");
            throw new NotLoggedInException("Only logged in users can reply to posts.");
		}
		return this.ph.replyToPost(parentId, header, pc);
	}

	/*
	 * (non-Javadoc)
	 * @see forum.server.business.ForumFacadeInt#promoteModerator(java.lang.String)
	 */
	public Moderator promoteModerator(String[] usernames)
	throws NotLoggedInException, UnauthorizedOpException,
	NoSuchUserException, DatabaseException {
		return this.uh.promoteModerator(usernames);
	}
	
	/*
	 * same as abobe just for a single user.
	 */
	public Moderator promoteModerator(String username)
	throws NotLoggedInException, UnauthorizedOpException,
	NoSuchUserException, DatabaseException {
		return this.uh.promoteModerator(username);
	}

	/*
	 * (non-Javadoc)
	 * @see forum.server.business.ForumFacadeInt#demoteModerator(java.lang.String)
	 */
	public LoggedInUser demoteModerator(String[] usernames)
	throws NotLoggedInException, UnauthorizedOpException,
	NoSuchUserException, DatabaseException {
		return this.uh.demoteModerator(usernames);
	}
	
	/*
	 * same as above just for a single user.
	 */
	public LoggedInUser demoteModerator(String username)
	throws NotLoggedInException, UnauthorizedOpException,
	NoSuchUserException, DatabaseException {
		return this.uh.demoteModerator(username);
	}

	/*
	 * (non-Javadoc)
	 * @see forum.server.business.ForumFacadeInt#searchByAuthor(java.lang.String, int, int)
	 */
	public String searchByAuthor(String username, int from, int to)
	throws NoSuchUserException, InvalidSearchRangeException {
		return encodeSearchHits(this.ph.searchByAuthor(username, from, to));
	}

	/*
	 * (non-Javadoc)
	 * @see forum.server.business.ForumFacadeInt#searchByContent(java.lang.String, int, int)
	 */
	public String searchByContent(String phrase, int from, int to)
	throws InvalidSearchRangeException {
		return encodeSearchHits(this.ph.searchByContent(phrase, from, to));


	}

    /**
     * Used by the 2 searchBy methods to avoid code duplication.
     *
     * @param shs
     * @return
     */
    private String encodeSearchHits(SearchHit[] shs) {
//    	String response = "<total>" + shs.length + "</total>\n";
        String response = shs.length + Delimiters.MSG_PARTS;
        String d = Delimiters.SEARCH_HIT_PARTS;

        // sort serach hits according to score
        Arrays.sort(shs, new Comparator<SearchHit>() {
            public int compare(SearchHit o1, SearchHit o2) {
                return Double.compare(o1.getScore(), o2.getScore());
            }
        });

        for (SearchHit sh : shs) {
            if (sh != null) {
                Post p = sh.getPost();
                response += p.getAuthor() + d + p.getHeader() + d
                + p.getModifiedDate() + d + sh.getScore()*100 + d
                + p.getPostId() + Delimiters.MSG_PARTS;
//                try {
//                    response += getRepliesView(p);
//                } catch (DatabaseException ex) {
//                    return null;
//                }
            }
        }

        return response;
    }

	/*
	 * (non-Javadoc)
	 * @see forum.server.business.ForumFacadeInt#getAllRegisteredUsers()
	 */
	@Override
	public Vector<String> getAllRegisteredUsers() throws DatabaseException{
		Vector<String> result=new Vector<String>();
		Vector<LoggedInUser> list=this.uh.getAllUsers();
		for(LoggedInUser l:list){
			result.add(l.getUsername());
		}
		return result;
	}

	/*
	 * (non-Javadoc)
	 * @see forum.server.business.ForumFacadeInt#getModerators()
	 */
	@Override
	public Vector<String> getModerators() throws DatabaseException {
		Vector<String> result=new Vector<String>();
		Vector<Moderator> list=this.uh.getModerators();
		for(Moderator m:list){
			result.add(m.getUsername());
		}
		return result;
	}

	/*
	 * (non-Javadoc)
	 * @see forum.server.business.ForumFacadeInt#getAllUsersForPromotion()
	 */
	@Override
	public Vector<String> getAllUsersForPromotion() throws DatabaseException {
		Vector<String> result=new Vector<String>();
		Vector<LoggedInUser> list=this.uh.getAllUsersForPromotion();
		for(LoggedInUser l:list){
			result.add(l.getUsername());
		}
		return result;
	}

    public void deletePost(int postId) throws NotLoggedInException,
            NoSuchPostException, UnauthorizedOpException, DatabaseException {
    	if (this.loggedInUser == null) {
			logger.logInfo("Modify post: A guest user trying to modify a post.");
            throw new NotLoggedInException("Only logged in users can delete posts.");
		}
		this.ph.deletePost(postId);
    }


	/*
	 * (non-Javadoc)
	 * @see forum.server.business.ForumFacadeInt#getConnectedUsers()
	 */
	@Override
	public Vector<String> getConnectedUsers() throws DatabaseException {
		return this.uh.getConnectedRegisteredUsers();
	}


	@Override
	public void decGuestCount() throws DatabaseException {
		this.uh.decGuestCount();
	}

	@Override
	public int getGuestCount() throws DatabaseException {
		return this.uh.getGuestCount();
		
	}

	@Override
	public Vector<String> getOnlineUsers() throws DatabaseException {
		return this.uh.getConnectedRegisteredUsers();
	}

	@Override
	public void incGuestCount() throws DatabaseException {
		this.uh.incGuestCount();
	}

}
